package com.hjc.oj.service.impl;

import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONConfig;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.hjc.oj.common.ErrorCode;
import com.hjc.oj.exception.BusinessException;
import com.hjc.oj.model.dto.wx.WxmpQrcodeAndUuid;
import com.hjc.oj.model.entity.User;
import com.hjc.oj.model.enums.UserRoleEnum;
import com.hjc.oj.model.vo.LoginUserVO;
import com.hjc.oj.service.MyWxMpService;
import com.hjc.oj.service.UserService;
import lombok.extern.log4j.Log4j2;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.common.bean.WxOAuth2UserInfo;
import me.chanjar.weixin.common.bean.oauth2.WxOAuth2AccessToken;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import static com.hjc.oj.constant.UserConstant.USER_LOGIN_STATE;


@Log4j2
@Service
public class MyWxMpServiceImpl implements MyWxMpService {

    @Resource
    private WxMpService wxMpService;
    @Resource
    private UserService  userService;

    @Value("${wx.mp.appId}")
    private String appId;
    @Value("${wx.mp.secret}")
    private String secret;
    @Value("${myBack.url}")
    private String myBackUrl;
    /**
     * 盐值，混淆密码
     */
    @Value("${getPassword.salt}")
    private String SALT ;

    //展示存放微信扫码登录的用户
    public static final ConcurrentHashMap<String, User> CONCURRENT_HASH_MAP = new ConcurrentHashMap<>();
    //获取或创建一个定时任务线程池（建议全局单例，避免频繁创建销毁线程）
    private static final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();


    /***获取二维码url和uuid
     *
     * @return
     */
    @Override
    public WxmpQrcodeAndUuid getQrcodeAndUuid() {
        //生成uuid记录使用当前二维码登录的用户
        String uuid = UUID.randomUUID().toString();
        //将uuid存放到concurrenhashmap中，并开启一个进程定时，2分钟后失效，删除uuid
        CONCURRENT_HASH_MAP.put(uuid,new User());
        // 提交定时任务（延迟2分钟执行）
        scheduler.schedule(() -> {
            // 定时任务内的代码：2分钟后执行
            CONCURRENT_HASH_MAP.remove(uuid);
            log.warn("定时任务：UUID 已删除->"+uuid);
        }, 30, TimeUnit.SECONDS);

        //回调地址转换为 URLEncode格式
        //String redirect_uri = URLEncoder.encode("http://1822d679.r19.vip.cpolar.cn/api/myWxMp/wxMpCallBack","UTF-8");
        //https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxd98a3afe5ad4158a&redirect_uri=http%3A%2F%2Fdevelopers.weixin.qq.com&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect
        //String url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxd98a3afe5ad4158a&redirect_uri="+redirect_uri+"&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect" ;
        String redirect_uri = myBackUrl+"/api/myWxMp/wxMpCallBack";
        //uuid存放到state中，会返回给回调函数
        String qrcodeUrl = wxMpService.getOAuth2Service().buildAuthorizationUrl(redirect_uri, WxConsts.OAuth2Scope.SNSAPI_USERINFO, uuid);
        WxmpQrcodeAndUuid wxmpQrcodeAndUuid = new WxmpQrcodeAndUuid(uuid,qrcodeUrl);
        return wxmpQrcodeAndUuid;
    }

    /***用户扫码后的通过code 获取 微信用户信息并将信息存放到concurrenthashMap中
     * @param code
     * @return
     * @throws WxErrorException
     */
    @Override
    public User wxMpGetUser(String code) throws WxErrorException {
       log.warn(code+"获取微信用户信息 ");
        //根据code获取用户信息
//        code：作为换取access_token的票据，每次用户授权带上的code将不一样，code只能使用一次，5分钟未被使用自动过期。
//        state：直接带上授权链接的 state 参数内容，用于防止越权行为。
//        User currUser = wxService.getUserInfo(code,state);    //获取用户信息
        User currUser = new User();
        WxOAuth2AccessToken wxOAuth2AccessToken = wxMpService.getOAuth2Service().getAccessToken(code);
//        wxOAuth2AccessToken = wxMpService.getOAuth2Service().refreshAccessToken(wxOAuth2AccessToken.getRefreshToken());        //刷新token
        WxOAuth2UserInfo wxOAuth2UserInfo = wxMpService.getOAuth2Service().getUserInfo(wxOAuth2AccessToken, null);
        System.out.println(wxOAuth2UserInfo);
        if (StringUtils.isAnyBlank(wxOAuth2UserInfo.getOpenid())) {
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "登录失败，系统错误");
        }
        User user = new User();
        user.setUnionId(wxOAuth2UserInfo.getUnionId());
        user.setMpOpenId(wxOAuth2UserInfo.getOpenid());
        user.setUserAvatar(wxOAuth2UserInfo.getHeadImgUrl());
        user.setUserName(wxOAuth2UserInfo.getNickname());
        return user;
    }

    /***用户登录（微信公众号） 过时
     *
     * @param wxMpUser
     * @param uuid LoginUserVO
     * @return
     */
    @Override
    public LoginUserVO userLoginByMp(User wxMpUser,String uuid) {
        String mpOpenId = wxMpUser.getMpOpenId();
        // 单机锁   todo unionId
        synchronized (mpOpenId.intern()) {
            // 查询用户是否已存在
            QueryWrapper<User> queryWrapper = new QueryWrapper<>();
            //queryWrapper.eq("unionId", unionId);
            //在微信生态中， unionid 是用户在微信开放平台下的唯一标识。
            //openid 是用户在单个微信应用（如服务号、小程序、APP） 内的唯一标识
            //unionid 只有在用户将服务号绑定到微信开放平台账号后，才会出现该字段。
            //本人目前使用的是测试公众号，没有unionid
            queryWrapper.eq("mpOpenId", mpOpenId);
            User user = userService.getOne(queryWrapper);
            // 被封号，禁止登录
            if (user != null && UserRoleEnum.BAN.getValue().equals(user.getUserRole())) {
                throw new BusinessException(ErrorCode.FORBIDDEN_ERROR, "该用户已被封，禁止登录");
            }
            // 用户不存在则创建
            if (user == null) {
                user = new User();
                //  密码加密
                String encryptPassword = DigestUtils.md5DigestAsHex((SALT + 12345678).getBytes());
                user.setUserPassword(encryptPassword);
                //创建默认账号
                String userAccount = generateDefaultAccount(mpOpenId);
                user.setUserAccount(userAccount);
                user.setUnionId(wxMpUser.getUnionId());
                user.setMpOpenId(mpOpenId);
                user.setUserAvatar(wxMpUser.getUserAvatar());
                user.setUserName(wxMpUser.getUserName());
                System.out.println("==============================");
                System.out.println(user);
                boolean result = userService.save(user);
                if (!result) {
                    throw new BusinessException(ErrorCode.SYSTEM_ERROR, "登录失败");
                }
            }
            // 记录用户的登录态
            CONCURRENT_HASH_MAP.put(uuid,user);

            LoginUserVO loginUserVO = userService.getLoginUserVO(user);

            return loginUserVO;
        }
    }

    /**
     * 前端轮询调用，通过uuid判断当前用户是否登录
     * @param uuid
     * @return
     */
    @Override
    public int wxMpUserIsLogin(String uuid, HttpServletRequest request) {
        System.out.println("uuid+获取====="+CONCURRENT_HASH_MAP.get(uuid));
        System.out.println("=======================");
        if(CONCURRENT_HASH_MAP.get(uuid) == null){
            //uuid的key不存在 ,二维码过期
            return -1;
        }
        if(CONCURRENT_HASH_MAP.get(uuid).getId() == null){
            //uuid的key存在 ,但是loginUserVo没有值，微信用户未登录
            return 0;
        }else{
            System.out.println("用户已经登录++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
            request.getSession().setAttribute(USER_LOGIN_STATE, CONCURRENT_HASH_MAP.get(uuid));
            System.out.println(request.getSession().getAttribute(USER_LOGIN_STATE));
            return 1;
        }
    }


    /**
     * 生成默认账号：oj + 总用户量 + openid前两位（处理后）
     * @param openid 用户的openid（微信唯一标识）
     * @return 符合规则的默认账号
     */
     private  String generateDefaultAccount (String openid) {
            // 1. 固定前缀 "oj"
            String prefix = "oj";

            // 2. 处理openid前两位：过滤非法字符，确保仅包含字母、数字、下划线
            String openidPrefix ;
            if (openid == null || openid.isEmpty()) {
                openidPrefix = "oj";
            }else{
                // 正则匹配：保留 a-zA-Z0-9_，其他字符替换为空
                String validOpenid =  openid.replaceAll("[^a-zA-Z0-9_]", "");
                if (validOpenid.length() >= 2) {
                    openidPrefix = validOpenid.substring(0, 2);
                }else{
                    openidPrefix = "oj";
                }

            }
            // 3. 拼接账号：oj + 总用户量 + openid前两位
            long totalUserCount =  userService.count();
            String account = openidPrefix+prefix+ totalUserCount ;

            // 4. 确保长度在4-12位之间
            if (account.length() > 12) {
                throw new BusinessException(ErrorCode.SYSTEM_ERROR,"用户过多");
            }
            return account;
        }

    /***    Wxmp 微信公众获取用户信息  原生实现
     *
     * @param code  作为换取access_token的票据，每次用户授权带上的code将不一样，code只能使用一次，5分钟未被使用自动过期。
     * @param state 直接带上授权链接的 state 参数内容，用于防止越权行为。
     * @return
     */
    @Override
    public User getUserInfo(String code,String state) throws WxErrorException {
        /***4. 获取用户授权信息TOKEN
         * 通过 code 换取的是用户授权 access_token，它的权限范围仅限于该用户的信息获取，无法调用本文档之外的其他接口。此接口返回的 Token 信息可用范围如下：
         * 在 snsapi_base 情况下，无法也不需要调用任何接口。
         * 在 snsapi_userinfo 情况下，可以执行 获取用户信息、刷新 Token、检验 Token 有效性
         * GET https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=
         *authorization_code
         */
        String tokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token?appid="+appId
                +"&secret="+secret+"&code="+code
                +"&grant_type=authorization_code";
        String tokenResponseStr = HttpUtil.createGet(tokenUrl)
                .execute()
                .body();
                WxOAuth2AccessToken wxTokenInfo = JSONUtil.toBean(tokenResponseStr,WxOAuth2AccessToken.class);
//        {返回类型
//            "access_token":"ACCESS_TOKEN",
//                "expires_in":7200,
//                "refresh_token":"REFRESH_TOKEN",
//                "openid":"OPENID",
//                "scope":"SCOPE",
//                "is_snapshotuser": 1,
//                "unionid": "UNIONID"
//        }
        System.out.println(wxTokenInfo);
        /***4.2 刷新授权 Token
         由于 access_token 拥有较短的有效期，当 access_token 超时后，可以使用 refresh_token 进行刷新，refresh_token 有效期为30天，当 refresh_token 失效之后，需要用户重新授权。
         GET https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=APPID&grant_type=refresh_token&refresh_token=
         REFRESH_TOKEN
         */


        /***4.3 检验 Token 有效性
         * 可使用此接口，检验上面的授权 access_token 是否有效
         * GET https://api.weixin.qq.com/sns/auth?access_token=ACCESS_TOKEN&openid=
         * OPENID
         */


        /*** 5.拉取用户信息
         *如果网页授权作用域为 snsapi_userinfo，
         * 则此时可以通过 access_token 和 openid 拉取用户信息。
         * GET https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN
         */
         String getUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo?access_token="+wxTokenInfo.getAccessToken()
                +"&openid="+wxTokenInfo.getOpenId()
                +"&lang=zh_CN";
        String wxUserInforesponseStr = HttpUtil.createGet(getUserInfoUrl)
                .execute()
                .body();
        System.out.println(wxUserInforesponseStr);
        // 正确写法：添加字段映射（JSON字段名 -> 实体类属性名）
        Map<String, String> fieldMap = new HashMap<>();
        fieldMap.put("headimgurl", "headImgUrl"); // JSON的headimgurl 对应 实体的headImgUrl
//        创建JSON配置并设置字段映射
        JSONConfig config = JSONConfig.create();
        config.setIgnoreCase(true);
        WxOAuth2UserInfo wxUser = JSONUtil.toBean(wxUserInforesponseStr,config,WxOAuth2UserInfo.class);


        System.out.println(wxUser);

        User user = new User();
        user.setMpOpenId(wxUser.getOpenid());
        user.setUserName(wxUser.getNickname());
        user.setUserAvatar(wxUser.getHeadImgUrl());
        user.setUnionId(wxUser.getUnionId());
        //判断用户是否注册，没注册默认注册
        return user;
    }

}
